package busuanzi.module;


import busuanzi.storage.MongoClient;
import busuanzi.util.Executors2;
import busuanzi.util.Logs;
import com.alibaba.fastjson.JSON;
import com.google.common.base.Strings;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.mongodb.client.model.UpdateOptions;
import org.bson.Document;

import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

import static busuanzi.storage.MongoClient.s;
import static sardine.Sardine.get;

/**
 * jstat -gcutil  pid  3000 100  : 3000ms统计一次gc情况统计100次；
 * <p/>
 * Auth: bruce-sha
 * Date: 2015/4/29
 */
public abstract class Statistics {

    static final ExecutorService executor = Executors2.ioCost();
    static final String startTime = LocalDateTime.now().toString();

    static void asynStatsRegisters(final String host) {
        executor.submit(() -> {
            MongoClient.wash("busuanzi_stats/registers", busuanzi_stats_registers -> {
                // 如何合并成一个动作
                Document existHost = busuanzi_stats_registers.find(
                        s("{_id: '%s'}", host)).limit(1).first();
                if (existHost == null)
                    busuanzi_stats_registers.insertOne(
//                            s("{_id: '%s',date: '%s',dateAsInteger: %s}", host, today(), todayAsInteger()));
                            s("{_id: '%s',date: '%s'}", host, today()));
            });

            //TODO 
//            qps(host);
        });
    }

    static void asynStatsVisits() {
        executor.submit(() -> {
            MongoClient.wash("busuanzi_stats/visits", busuanzi_stats_visits -> {
                busuanzi_stats_visits.updateOne(
                        s("{_id: '%s'}", today()),
                        s("{$inc: {count: 1}}")
                        , new UpdateOptions().upsert(true));
            });
        });
    }

    final static Cache<Long, String> QPS = CacheBuilder.newBuilder()
            .softValues()
            .removalListener(notification -> {
                Logs.log("QPS=" + notification.getCause().toString() + "," + notification.getKey());
            })
            .expireAfterWrite(1, TimeUnit.HOURS)
            .build();

    final static Cache<String, String> QPS_Stats = CacheBuilder.newBuilder()
            .softValues()
            .removalListener(notification -> {
                Logs.log("QPS_Stats=" + notification.getCause().toString() + "," + notification.getKey());
            })
            .expireAfterWrite(1, TimeUnit.DAYS)
            .build();

    static final AtomicLong c = new AtomicLong(Long.MIN_VALUE);

    static void qps(String host) {
        c.compareAndSet(Long.MAX_VALUE, Long.MIN_VALUE);
        QPS.put(c.incrementAndGet(), "");
        String time = LocalDateTime.now().withSecond(0).withNano(0).toString();
        if (time.endsWith("0") || time.endsWith("00") || time.endsWith("30")) {
            final DecimalFormat nf = new DecimalFormat("0.000");
//            QPS_Stats.put(time, nf.format(qps()));
            try {
                QPS_Stats.get(time, () -> nf.format(qps()));
            } catch (Exception e) {
                Logs.error(e, "qps error.");
            }
        }
    }

    static double qps() {
        return (double) QPS.size() / TimeUnit.HOURS.toSeconds(1);
    }


    public static void run() {

        get("/stats", "application/json", (request, response) -> {
            return MongoClient.bath("busuanzi/busuan", busuans -> {
                List<Stats> re = busuans.find(s("{path: '_pv'}"))
                        .sort(s("{lastModified: -1}"))
                        .map(document -> new Stats(
                                        document.getString("host"),
                                        document.getInteger("count"),
                                        document.getDate("lastModified"))
                        ).into(new LinkedList<Stats>());
                re.add(0, new Stats("total", re.size(), new Date()));
                return _html5("", re.subList(0, 100));
            });
        });


        get("/stats2", (request, response) -> {
            Map<String, Object> re = new LinkedHashMap<String, Object>();

            re.put("daily visits",
                    MongoClient.bath("busuanzi_stats/visits", busuanzi_stats_visits ->
                                    busuanzi_stats_visits
                                            .find()
                                            .sort(s("{_id: -1}"))
                                            .limit(10)
                                            .map(
                                                    document -> document.getString("_id") + ": " + document.getInteger("count")
                                            ).into(new LinkedList<String>())
                    ));

            re.put("daily registers",
                    MongoClient.bath("busuanzi_stats/registers", busuanzi_stats_registers -> {

                        final List<Document> pipeline = new ArrayList<Document>();
                        pipeline.add(s("{$group : {_id: '$date', count: {$sum : 1}}}"));
                        pipeline.add(s("{$sort : {_id: -1}}"));//必须先sort，必须_id
                        pipeline.add(s("{$limit : 20}"));

                        return busuanzi_stats_registers
                                .aggregate(pipeline)
                                .map(
                                        document -> document.getString("_id") + ": " + document.getInteger("count")
                                ).into(new LinkedList<String>());
                    }));

            return html5("stats2", re);
        });

        get("/qps", () -> {
            final Map<String, Object> re = new LinkedHashMap<String, Object>();
            re.put("start", Arrays.asList(startTime, LocalDateTime.now().toString()));
            re.put("hourly qps", qps());
            re.put("daily qps", QPS_Stats.asMap().entrySet().stream().map(
                    e -> e.getKey() + "=" + e.getValue()
            ).sorted().collect(Collectors.toList()));

            return html5("qps", re);
        });

        get("/qps2", () -> {
            final Map<String, Object> re = new LinkedHashMap<String, Object>();
            re.put("QPS_Stats", QPS_Stats.asMap());
            return html5("qps", re);
        });

        get("/nocors", (request, response) -> {
            return "nocors";
        });

        get("/cors", (request, response) -> {

            response.header("Access-Control-Allow-Credentials", "true");
            response.header("Access-Control-Allow-Origin", "*");
            response.header("Access-Control-Allow-Origin", "http://localhost:9527");
            response.header("Access-Control-Allow-Headers",
                    "Origin, Accept-Language, Accept-Encoding, X-Forwarded-For, Connection, Accept, User-Agent, " +
                            "Host, Referer, Cookie, Content-Type, Cache-Control, If-Modified-Since, *");
            response.header("Access-Control-Expose-Headers", "Date");

            return "cors";
        });


//        get("/stats/detail", "application/json", (request, response) -> {
//            return RedisPool.bath((jedis) -> {
//                List<Stats> re = Lists.newArrayList();
//                // 获取所有站点
//                Set<String> sites = jedis.keys("*");
//
//                for (String site : sites) {
//
//                    final Stats stats = new Stats();
//                    stats.site = site;
//
//                    Set<Tuple> pages = jedis.zrangeWithScores(site, 0, -1);
//
//                    final List<Map> pagesModel = Lists.newArrayList();
//
//                    for (Tuple pt : pages) {
//
//
//                        String page = pt.getElement();
//                        double counter = pt.getScore();
//
//                        pagesModel.add(Collections.singletonMap(page, counter));
//                    }
//
//                    stats.pages = pagesModel;
//                    stats.site = site;
//                    re.add(stats);
//                }
//
//                return re;
//            });
//        }, HtmlJsonTransformer);
    }


    private static String today() {
        return LocalDate.now().toString();
    }

    private static long todayAsInteger() {
        return Long.valueOf(LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE));
    }


    private static String _html5(String title, Object body) {
        return "<!DOCTYPE HTML>" +
                "<html>" +
                " <head>" +
                "  <meta charset='utf-8'/>" +
                "  <meta name='viewport' content='width=device-width, initial-scale=1'/>" +
                "  <meta http-equiv='refresh' content='8'/>" +
                "  <title>" + title + "</title>" +
                " </head>" +
                "<body>" +
                (Strings.isNullOrEmpty(title) ? "" : (" <h3>" + title + "</h3>")) +
                JSON.toJSONString(body, true).replaceAll("\n", "<br>").replaceAll("\t", "&nbsp;&nbsp;") +
                "</body>" +
                "</html>";
    }

    private static String html5(String title, Map<String, Object> content) {
        final StringBuilder sb = new StringBuilder();
        sb.append("<!DOCTYPE HTML>");
        sb.append("<html>");
        sb.append(" <head>");
        sb.append("  <meta charset='utf-8'/>");
        sb.append("  <meta name='viewport' content='width=device-width, initial-scale=1'/>");
        sb.append("  <meta http-equiv='refresh' content='8'/>");
        sb.append("  <title>").append(title).append("</title>");
        sb.append(" </head>");
        sb.append("<body>");

        for (Map.Entry<String, Object> c : content.entrySet()) {
            String key = c.getKey();
            Object value = c.getValue();
            if (!key.isEmpty()) sb.append(" <h3>").append(key).append("</h3>");
            sb.append(JSON.toJSONString(value, true).replaceAll("\n", "<br>").replaceAll("\t", "&nbsp;&nbsp;"));
        }

        sb.append("</body>");
        sb.append("</html>");

        return sb.toString();
    }

    public static class Stats/* implements Comparable<Stats>*/ {
        String host;
        int count;
        String lastModified;

        public Stats(String host, int count, Date lastModified) {
            this.host = host;
            this.count = count;
            if (lastModified == null) {
                Calendar ca = Calendar.getInstance();
                ca.set(2000, 1, 1);
                lastModified = ca.getTime();
            }
            this.lastModified = simpleFormat.format(lastModified);
        }

        public String getHost() {
            if ("total".equalsIgnoreCase(host)) return host;
            return String.format("<a href='http://%s' target='_blank'>%s</a>", host, host);
        }

        public int getCount() {
            return count;
        }

        public String getLastModified() {
            return lastModified;
        }

       /* public int compareTo(Stats o) {
            int t = o.lastModified.compareTo(lastModified);
            if (t == 0)
                return o.host.compareTo(host);
            else
                return t;
        }*/

//        @Override
//        public String toString() {
//            return String.format("%s   '%s':%s", lastModified, host, count);
//        }

        final SimpleDateFormat simpleFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    }

}
